#include "noria/core.h"
using namespace noria;

#include <string>
#include <mutex>
#include <unordered_map>
using namespace std;


//int NcGetCachedObject(const char* library, const char* type, void** reflectable, int objectId);
//int NcCreate(const char* library, const char* type, void** reflectable);
static inline int GetSystemCachedObject(int objectId, void** reflectable);

class CacheMap final
{
public:
	//CacheMap()
	//	: m_mx(new mutex())
	//{
	//}
	//CacheMap(CacheMap&& ref)
	//	: m_map(move(ref.m_map)), m_mx(move(ref.m_mx))
	//{
	//	ref.m_mx = nullptr;
	//}
	//~CacheMap()
	//{
	//	delete m_mx;
	//	m_mx = nullptr;
	//}
public:
	void Lock()
	{
		//m_mx->lock();
	}
	void Unlock()
	{
		//m_mx->unlock();
	}
	bool Exist(const string& type) const
	{
		return m_map.find(type) != m_map.end();
	}
	IReflectable* Get(const string& type) const
	{
		auto it = m_map.find(type);
		if (it != m_map.end())
		{
			return it->second.Get();
		}
		return nullptr;
	}
	void Add(const string& type, IReflectable* reflectable)
	{
		m_map.insert({ type, IReflectablePtr(reflectable) });
	}

private:
	unordered_map<string, IReflectablePtr> m_map;
	//mutex* m_mx;
};

int NcGetCachedObject(const char* library, const char* type, void** reflectable, int objectId)
{
	static unordered_map<string, CacheMap> cache;

	// system object will be treated by special logic to improve performance
	if (objectId != 0)
	{
		return GetSystemCachedObject(objectId, reflectable);
	}

	string lib(library);
	string tc(type);

	auto it = cache.find(lib);
	if (it == cache.end())
	{
		// !! 'static' is not thread safe
		static mutex mx;
		{
			lock_guard<mutex> lock(mx);

			it = cache.find(lib);
			if (it == cache.end())
			{
				cache.insert({ lib, {} });
			}
		}

		it = cache.find(lib);
	}

	// assertion?
	if (it == cache.end())
	{
		*reflectable = nullptr;
		return 1;
	}

	// create or get the existing object
	if (!it->second.Exist(tc))
	{
		int hr = 0;

		it->second.Lock();
		{
			if (!it->second.Exist(tc))
			{
				IReflectable* ptr;
				hr = NcCreate(library, type, reinterpret_cast<void**>(&ptr));
				if (hr == 0)
				{
					it->second.Add(tc, ptr);
				}
			}
		}
		it->second.Unlock();
		if (hr != 0)
		{
			return hr;
		}
	}

	// get the interface
	*reflectable = it->second.Get(tc);
	return 0;
}

int GetSystemCachedObject(int objectId, void** reflectable)
{
	static IMarshalPtr marshal = []{
		void* ptr;
		NcCreate("libnc", "noria.Marshal", &ptr);
		return static_cast<IMarshal*>(ptr);
	}();

	switch (objectId)
	{
	case SystemObject::_Marshal: *reflectable = &marshal;
	default: return -1;
	}

	return 0;
}